using System;
using System.Collections.Generic;
using System.Linq;
using StackCalc.Instructions;

namespace StackCalc
{
	/// <summary>
	/// The actual stack engine.
	/// </summary>
	public class StackEngine
	{
		/// <summary>
		/// Represents the actual stack.
		/// </summary>
		private readonly List<IInstruction> _stack = new List<IInstruction>();

		public void Process(IInstruction instruction)
		{
			// execute the instruction
			instruction.Execute(_stack, this);
		}

		public string ErrorText { get; private set; }

		public string PrintStack()
		{
			// aggregate over string representations of stack elements in reverse order
			IEnumerable<string> stackInstructions = _stack.Select(x => x.ToString());
			return stackInstructions.Reverse().Aggregate(string.Empty, (s, c) => s + c + "\n");
		}

		public void Reset()
		{
			_stack.Clear();
		}

		public bool HasSemanticError(IInstruction instruction)
		{
			ErrorText = instruction.Validate(_stack);

			if (string.IsNullOrEmpty(ErrorText))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		/// <summary>
		/// Happens when an apply action was triggered.
		/// </summary>
		public event EventHandler<EventArgs<string>> ApplyEvent;

		public void InvokeApplyEvent(EventArgs<string> e)
		{
			EventHandler<EventArgs<string>> handler = ApplyEvent;
			if (handler != null) handler(this, e);
		}

		/// <summary>
		/// Happens when something is printed.
		/// </summary>
		public event EventHandler<EventArgs<string>> PrintEvent;

		public void InvokePrintEvent(EventArgs<string> e)
		{
			EventHandler<EventArgs<string>> handler = PrintEvent;
			if (handler != null) handler(this, e);
		}

		/// <summary>
		/// Happens when something is printed.
		/// </summary>
		public event EventHandler<EventArgs<Action<string>>> ReadEvent;

		public void InvokeReadEvent(EventArgs<Action<string>> e)
		{
			EventHandler<EventArgs<Action<string>>> handler = ReadEvent;
			if (handler != null) handler(this, e);
		}
	}

	/// <summary>
	/// Some useful extension methods.
	/// </summary>
	public static class StackExtensions
	{
		/// <summary>
		/// Peeks at the nth instruction from top (indexing starts with 1).
		/// </summary>
		public static IInstruction PeekInstructionFromTop(this List<IInstruction> stack, int n)
		{
			return stack[stack.Count - n];
		}

		/// <summary>
		/// Gets the nth instruction from top (indexing starts with 1).
		/// </summary>
		public static IInstruction PopInstructionFromTop(this List<IInstruction> stack, int n)
		{
			IInstruction instruction = stack[stack.Count - n];

			stack.RemoveAt(stack.Count - n);

			return instruction;
		}
	}

	/// <summary>
	/// Typed event args.
	/// </summary>
	/// <typeparam name="T">Type.</typeparam>
	public class EventArgs<T> : EventArgs
	{
		public EventArgs(T value)
		{
			Value = value;
		}

		public T Value { get; private set; }
	}
}
